/* Coordinate:
 * default coordinate of canvas start at left-top corner
 * the firework coordinate start at left-bottom, range from [0, 1] on x and [0, 1/aspect ] on y
 */

const { createApp, reactive, toRefs } = Vue;

const ColorSet = [
    "AliceBlue", "AntiqueWhite", "Aqua", "Aquamarine", "Azure", 
    "Beige", "Bisque", "Black", "BlanchedAlmond", "Blue", "BlueViolet",
    "Brown", "BurlyWood", "CadetBlue", "Chartreuse", "Chocolate", 
    "Coral", "CornflowerBlue", "Cornsilk", "Crimson", "Cyan", "DarkBlue", 
    "DarkCyan", "DarkGoldenRod", "DarkGray", "DarkGreen", "DarkKhaki", 
    "DarkMagenta", "DarkOliveGreen", "DarkOrange", "DarkOrchid", 
    "DarkRed", "DarkSalmon", "DarkSeaGreen", "DarkSlateBlue", 
    "DarkSlateGray", "DarkTurquoise", "DarkViolet", "DeepPink", 
    "DeepSkyBlue", "DimGray", "DodgerBlue", "FireBrick", "FloralWhite", 
    "ForestGreen", "Fuchsia", "Gainsboro", "GhostWhite", "Gold", 
    "GoldenRod", "Gray", "Green", "GreenYellow", "HoneyDew", 
    "HotPink", "IndianRed", "Indigo", "Ivory", "Khaki", "Lavender", 
    "LavenderBlush", "LawnGreen", "LemonChiffon", "LightBlue", 
    "LightCoral", "LightCyan", "LightGoldenRodYellow", "LightGray", 
    "LightGreen", "LightPink", "LightSalmon", "LightSeaGreen", 
    "LightSkyBlue", "LightSlateGray", "LightSteelBlue", "LightYellow", 
    "Lime", "LimeGreen", "Linen", "Magenta", "Maroon", "MediumAquaMarine", 
    "MediumBlue", "MediumOrchid", "MediumPurple", "MediumSeaGreen", 
    "MediumSlateBlue", "MediumSpringGreen", "MediumTurquoise", "MediumVioletRed", 
    "MidnightBlue", "MintCream", "MistyRose", "Moccasin", "NavajoWhite", 
    "Navy", "OldLace", "Olive", "OliveDrab", "Orange", "OrangeRed", 
    "Orchid", "PaleGoldenRod", "PaleGreen", "PaleTurquoise", "PaleVioletRed", 
    "PapayaWhip", "PeachPuff", "Peru", "Pink", "Plum", "PowderBlue", 
    "Purple", "Red", "RosyBrown", "RoyalBlue", "SaddleBrown", "Salmon", 
    "SandyBrown", "SeaGreen", "SeaShell", "Sienna", "Silver", "SkyBlue", 
    "SlateBlue", "SlateGray", "Snow", "SpringGreen", "SteelBlue", "Tan", 
    "Teal", "Thistle", "Tomato", "Turquoise", "Violet", "Wheat", "White", 
    "WhiteSmoke", "Yellow", "YellowGreen"
]

function randColor() {
    return ColorSet[Math.floor(Math.random() * ColorSet.length)];
}

const app = createApp({
    data() {
        return {
            g: 1,
            airResistParam: 3,
            timeScale: 1.0
        }
    },
});

let control = null

function testShotFirework() {
    let firework = new Firework();
    firework.init(200, randColor());
    fireworks.push(firework);
    console.log("Shot one Firework!");
}

window.onload = function() {
    control = app.mount("#left");
    console.log(control)

    setup()
    renderFrame()

    document.addEventListener("keyup", (e) => {
        if (e.key == " ") {
            testShotFirework()
        }
    })
}

const pi = 3.14159265358979

let fireworks = []

function _componentOp(a, b, op) {
    if (a.length && b.length) {
        if (a.length != b.length)
            throw "array size not match for adding";
        return a.map((v, i) => op(v, b[i]));
    }
    else if (a.length) {
        return a.map((v) => op(v, b));
    }
    else if (b.length) {
        return b.map((v) => op(v, a));
    }
    else {
        return op(a, b);
    }
}

function mult(v1, v2) {
    return _componentOp(v1, v2, (a, b) => a * b);
}

function add(v1, v2) {
    return _componentOp(v1, v2, (a, b) => a + b);
}

function length(v) {
    if (v.length) {
        return Math.sqrt(v.reduce((r, c) => r + c * c, 0));
    }
    else {
        return v;
    }
}

function normalize(v) {
    if (v.length) {
        let len = length(v);
        return mult(v, 1 / len);
    }
    else {
        return 1;
    }
}

function checkNaN(v) {
    if (v.length) {
        v.map(((t) => checkNaN(t)));
    }
    else {
        if (isNaN(v)) throw "NaN";
    }
}

class Particle {
    constructor(pos, velocity, color = "red", config = {}) {
        this.active = true;
        this.pos = pos;
        this.velocity = velocity;
        this.color = color;
        this.size = 10;
        this.config = config;
    }

    _getA() {
        let a = [0, -control.g];
        let lenV = length(this.velocity);
        if (!this.config.airResistFree) {
            let airResistA = mult(normalize(mult(this.velocity, -1)), lenV * lenV * control.airResistParam);
            a = add(a, airResistA);
        }
        checkNaN(a);
        return a;
    }

    update(dt) { // explicit Euler
        if (!this.active) return;

        this.pos = add(this.pos, mult(this.velocity, dt));
        const a = this._getA();
        this.velocity = add(this.velocity, mult(a, dt));
        checkNaN(this.pos);
        checkNaN(this.velocity);
        
        const offset = 0.2;
        if ((this.pos[0] < -offset && this.velocity[0] < 0)
            || (this.pos[1] < -offset && this.velocity[1] < 0)
            || (this.pos[0] > 1 + offset && this.velocity[0] > 0)) {
            this.active = false
        }
    }

    draw() {
        if (!this.active) return;

        drawSquare(this.pos, this.size, this.color);
    }
}

function randDirection() {
    return normalize(add(mult([Math.random(), Math.random(), Math.random()], 2), -1));
}

class Firework {
    constructor() {
        this.raiseParticle = null
        this.boomParticles = [];
        this.numParicles = 0
        this.color = "red"
    }

    _getVelocityY(startHeight) {
        const expectBoomHeightMin = 0.4 / aspect;
        const expectBoomHeightMax = 0.8 / aspect;
        let boomHeight = (Math.random() * (expectBoomHeightMax - expectBoomHeightMin)) + expectBoomHeightMin;
        let dx = boomHeight - startHeight;
        let velocityY = Math.sqrt(2 * control.g * dx);
        return velocityY;
    }
    
    init(numParicles, color = "red") {
        this.numParicles = numParicles
        this.color = color
        let startHeight = -0.2;
        let center = [Math.random(), startHeight];
        let velocity = [Math.random() * 0.01, this._getVelocityY(startHeight)];
        this.raiseParticle = new Particle(center, velocity, this.color, {
            airResistFree: true
        });
    }

    boom(pos) {
        const maxSpeed = 1.0;
        for (let i = 0; i < this.numParicles; ++i) {
            let direction = randDirection();
            let speed = Math.random() * maxSpeed;
            let velocity = mult(direction.slice(0, 2), speed);
            let particle = new Particle(pos, velocity, this.color);
            this.boomParticles.push(particle);
        }
    }

    update(dt) {
        if (this.raiseParticle) {
            this.raiseParticle.update(dt);
            if (this.raiseParticle.velocity[1] <= -control.g*0.1) {
                this.boom(this.raiseParticle.pos);
                this.raiseParticle = null;
            }
        }
        else {
            for (let particle of this.boomParticles) {
                particle.update(dt);
            }
        }
    }

    draw() {
        if (this.raiseParticle) {
            this.raiseParticle.draw()
        }
        else {
            for (let particle of this.boomParticles) {
                particle.draw();
            }
        }
    }
}

let canvas = null
let ctx = null
let aspect = 1.0

function toCanvasCoord(pos) {
    let x = pos[0] * canvas.width;
    let y = (1 - pos[1] * aspect ) * canvas.height;
    return [x, y];
}

function drawSquare(center, size, color = "red") {
    let centerCanvas = toCanvasCoord(center);
    ctx.fillStyle = color;
    ctx.fillRect(centerCanvas[0] - size / 2, centerCanvas[1] - size / 2, size, size);
}

function setup() {
    canvas = document.getElementById('canvas');
    ctx = canvas.getContext('2d');

    let delayTimer = null
    const delayResizeCanvas = function() {
        window.clearTimeout(delayTimer)
        delayTimer = window.setTimeout(() => {
            console.log("resize")
            canvas.width = canvas.clientWidth
            canvas.height = canvas.clientHeight
            aspect = canvas.width / canvas.height
        }, 300)
    }

    window.addEventListener("resize", delayResizeCanvas)
    delayResizeCanvas()
}

let lastTime = null
function renderFrame() {
    // clear
    ctx.fillStyle = "black";
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    for(let i = 0; i < fireworks.length; ++i){  // TODO: too slow, find a better way
        const firework = fireworks[i]
        if (firework.raiseParticle) continue;
        let active = false;
        for(let paritcle of firework.boomParticles) {
            if (paritcle.active) {
                active = true;
                break;
            }
        }
        if (!active) { 
            console.log("One firework faded.");
            fireworks.splice(i, 1);
        }
    }

    // draw
    let curTime = new Date().getTime();
    if (lastTime) {
        let dt = control.timeScale * (curTime - lastTime) / 1000;
        for(let firework of fireworks) {
            firework.update(dt);
            firework.draw();
        }
    }
    lastTime = curTime

    window.requestAnimationFrame(renderFrame);
}